/*
 * The MIT License
 *
 * Copyright 2015-2016 Coinmate.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.knowm.xchange.coinmate;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.knowm.xchange.coinmate.dto.account.CoinmateBalance;
import org.knowm.xchange.coinmate.dto.account.CoinmateBalanceDataEntry;
import org.knowm.xchange.coinmate.dto.account.CoinmateCurrencies;
import org.knowm.xchange.coinmate.dto.account.CoinmateCurrencyInfo;
import org.knowm.xchange.coinmate.dto.account.CoinmateWithdrawFee;
import org.knowm.xchange.coinmate.dto.marketdata.CoinmateOrderBook;
import org.knowm.xchange.coinmate.dto.marketdata.CoinmateOrderBookEntry;
import org.knowm.xchange.coinmate.dto.marketdata.CoinmateTicker;
import org.knowm.xchange.coinmate.dto.marketdata.CoinmateTickerData;
import org.knowm.xchange.coinmate.dto.marketdata.CoinmateTickers;
import org.knowm.xchange.coinmate.dto.marketdata.CoinmateTradeStatistics;
import org.knowm.xchange.coinmate.dto.marketdata.CoinmateTransactions;
import org.knowm.xchange.coinmate.dto.marketdata.CoinmateTransactionsEntry;
import org.knowm.xchange.coinmate.dto.metadata.CoinmateTradingPairs;
import org.knowm.xchange.coinmate.dto.metadata.CoinmateTradingPairsEntry;
import org.knowm.xchange.coinmate.dto.trade.CoinmateOpenOrders;
import org.knowm.xchange.coinmate.dto.trade.CoinmateOpenOrdersEntry;
import org.knowm.xchange.coinmate.dto.trade.CoinmateOrderHistoryEntry;
import org.knowm.xchange.coinmate.dto.trade.CoinmateTradeHistory;
import org.knowm.xchange.coinmate.dto.trade.CoinmateTransactionHistory;
import org.knowm.xchange.coinmate.dto.trade.CoinmateTransactionHistoryEntry;
import org.knowm.xchange.coinmate.dto.trade.CoinmateTransferDetail;
import org.knowm.xchange.coinmate.dto.trade.CoinmateTransferHistory;
import org.knowm.xchange.coinmate.dto.trade.CoinmateTransferHistoryEntry;
import org.knowm.xchange.currency.Currency;
import org.knowm.xchange.currency.CurrencyPair;
import org.knowm.xchange.dto.Order;
import org.knowm.xchange.dto.account.Balance;
import org.knowm.xchange.dto.account.FundingRecord;
import org.knowm.xchange.dto.account.Wallet;
import org.knowm.xchange.dto.marketdata.OrderBook;
import org.knowm.xchange.dto.marketdata.Ticker;
import org.knowm.xchange.dto.marketdata.Trade;
import org.knowm.xchange.dto.marketdata.Trades;
import org.knowm.xchange.dto.meta.CurrencyMetaData;
import org.knowm.xchange.dto.meta.ExchangeMetaData;
import org.knowm.xchange.dto.meta.InstrumentMetaData;
import org.knowm.xchange.dto.meta.WalletHealth;
import org.knowm.xchange.dto.trade.LimitOrder;
import org.knowm.xchange.dto.trade.MarketOrder;
import org.knowm.xchange.dto.trade.StopOrder;
import org.knowm.xchange.dto.trade.UserTrade;
import org.knowm.xchange.dto.trade.UserTrades;
import org.knowm.xchange.instrument.Instrument;
import org.knowm.xchange.service.trade.params.TradeHistoryParamsSorted;

/**
 * @author Martin Stachon
 */
public class CoinmateAdapters {

  /**
   * Adapts a CoinmateTicker to a Ticker Object
   *
   * @param coinmateTicker The exchange specific ticker
   * @param currencyPair (e.g. BTC/USD)
   * @return The ticker
   */
  public static Ticker adaptTicker(CoinmateTicker coinmateTicker, CurrencyPair currencyPair) {
    return adaptTicker(coinmateTicker.getData(), currencyPair);
  }

  public static List<Ticker> adaptTickers(CoinmateTickers coinmateTickers) {
    Map<String, CoinmateTickerData> tickerMap = coinmateTickers.getData();
    if (tickerMap == null || tickerMap.isEmpty()) {
      return Collections.emptyList();
    }

    return tickerMap.entrySet().stream()
        .map(entry -> adaptTicker(entry.getValue(), CoinmateUtils.getPair(entry.getKey())))
        .collect(Collectors.toList());
  }

  public static Ticker adaptTicker(CoinmateTickerData data, CurrencyPair currencyPair) {
    BigDecimal last = data.getLast();
    BigDecimal bid = data.getBid();
    BigDecimal ask = data.getAsk();
    BigDecimal high = data.getHigh();
    BigDecimal low = data.getLow();
    BigDecimal volume = data.getAmount();
    BigDecimal open = data.getOpen();
    BigDecimal change = data.getChange();
    Date timestamp = new Date(data.getTimestamp() * 1000L);

    return new Ticker.Builder()
        .instrument(currencyPair)
        .last(last)
        .bid(bid)
        .ask(ask)
        .high(high)
        .low(low)
        .volume(volume)
        .timestamp(timestamp)
        .open(open)
        .percentageChange(change)
        .build();
  }

  public static List<LimitOrder> createOrders(
      List<CoinmateOrderBookEntry> coinmateOrders,
      Order.OrderType type,
      CurrencyPair currencyPair) {
    List<LimitOrder> orders = new ArrayList<>(coinmateOrders.size());
    for (CoinmateOrderBookEntry entry : coinmateOrders) {
      LimitOrder order =
          new LimitOrder(type, entry.getAmount(), currencyPair, null, null, entry.getPrice());
      orders.add(order);
    }
    return orders;
  }

  public static OrderBook adaptOrderBook(
      CoinmateOrderBook coinmateOrderBook, CurrencyPair currencyPair) {
    List<LimitOrder> asks =
        createOrders(coinmateOrderBook.getData().getAsks(), Order.OrderType.ASK, currencyPair);
    List<LimitOrder> bids =
        createOrders(coinmateOrderBook.getData().getBids(), Order.OrderType.BID, currencyPair);

    return new OrderBook(null, asks, bids);
  }

  public static Trades adaptTrades(CoinmateTransactions coinmateTransactions) {
    List<Trade> trades = new ArrayList<>(coinmateTransactions.getData().size());

    for (CoinmateTransactionsEntry coinmateEntry : coinmateTransactions.getData()) {
      Trade trade = adaptTrade(coinmateEntry);
      trades.add(trade);
    }

    // TODO correct sort order?
    return new Trades(trades, Trades.TradeSortType.SortByID);
  }

  public static Trade adaptTrade(CoinmateTransactionsEntry coinmateEntry) {
    return Trade.builder()
        .originalAmount(coinmateEntry.getAmount())
        .instrument(CoinmateUtils.getPair(coinmateEntry.getCurrencyPair()))
        .price(coinmateEntry.getPrice())
        .timestamp(new Date(coinmateEntry.getTimestamp()))
        .type(typeToOrderTypeOrNull(coinmateEntry.getType()))
        .id(coinmateEntry.getTransactionId())
        .build();
  }

  public static Order.OrderType typeToOrderTypeOrNull(String type) {
    switch (type) {
      case "BUY":
      case "QUICK_BUY":
      case "INSTANT_BUY":
        return Order.OrderType.BID;
      case "SELL":
      case "QUICK_SELL":
      case "INSTANT_SELL":
        return Order.OrderType.ASK;
    }
    return null;
  }

  public static Wallet adaptWallet(CoinmateBalance coinmateBalance) {

    Map<String, CoinmateBalanceDataEntry> funds = coinmateBalance.getData();
    List<Balance> balances = new ArrayList<>(funds.size());
    for (String lcCurrency : funds.keySet()) {
      Currency currency = Currency.getInstance(lcCurrency.toUpperCase());
      Balance balance =
          new Balance(
              currency,
              funds.get(lcCurrency).getBalance(),
              funds.get(lcCurrency).getAvailable(),
              funds.get(lcCurrency).getReserved());

      balances.add(balance);
    }
    return Wallet.Builder.from(balances).build();
  }

  public static UserTrades adaptTransactionHistory(
      CoinmateTransactionHistory coinmateTradeHistory) {
    List<UserTrade> trades =
        coinmateTradeHistory.getData().stream()
            .map(
                entry ->
                    UserTrade.builder()
                        .type(typeToOrderTypeOrNull(entry.getTransactionType()))
                        .originalAmount(entry.getAmount())
                        .instrument(
                            CoinmateUtils.getPair(
                                entry.getAmountCurrency() + "_" + entry.getPriceCurrency()))
                        .price(entry.getPrice())
                        .timestamp(new Date(entry.getTimestamp()))
                        .id(Long.toString(entry.getTransactionId()))
                        .orderId(Long.toString(entry.getOrderId()))
                        .feeAmount(entry.getFee())
                        .feeCurrency(Currency.getInstance(entry.getFeeCurrency()))
                        .build())
            .collect(
                Collectors.toCollection(
                    () -> new ArrayList<>(coinmateTradeHistory.getData().size())));

    return new UserTrades(trades, Trades.TradeSortType.SortByTimestamp);
  }

  public static UserTrades adaptTradeHistory(CoinmateTradeHistory coinmateTradeHistory) {
    List<UserTrade> trades =
        coinmateTradeHistory.getData().stream()
            .map(
                entry ->
                    UserTrade.builder()
                        .type(typeToOrderTypeOrNull(entry.getType()))
                        .originalAmount(entry.getAmount())
                        .instrument(CoinmateUtils.getPair(entry.getCurrencyPair()))
                        .price(entry.getPrice())
                        .timestamp(new Date(entry.getCreatedTimestamp()))
                        .id(Long.toString(entry.getTransactionId()))
                        .orderId(Long.toString(entry.getOrderId()))
                        .feeAmount(entry.getFee())
                        .feeCurrency(CoinmateUtils.getPair(entry.getCurrencyPair()).getCounter())
                        .build())
            .collect(
                Collectors.toCollection(
                    () -> new ArrayList<>(coinmateTradeHistory.getData().size())));

    return new UserTrades(trades, Trades.TradeSortType.SortByTimestamp);
  }

  public static List<FundingRecord> adaptFundingHistory(
      CoinmateTransactionHistory coinmateTradeHistory,
      CoinmateTransferHistory additionalTransferData) {
    List<FundingRecord> fundings = new ArrayList<>();

    for (CoinmateTransactionHistoryEntry entry : coinmateTradeHistory.getData()) {
      FundingRecord.Type type;
      FundingRecord.Status status;

      switch (entry.getTransactionType()) {
        case "WITHDRAWAL":
        case "CREATE_VOUCHER":
          type = FundingRecord.Type.WITHDRAWAL;
          break;
        case "DEPOSIT":
        case "USED_VOUCHER":
        case "NEW_USER_REWARD":
        case "REFERRAL":
          type = FundingRecord.Type.DEPOSIT;
          break;
        default:
          // here we ignore the other types which are trading
          continue;
      }

      switch (entry.getStatus().toUpperCase()) {
        case "OK":
        case "COMPLETED":
          status = FundingRecord.Status.COMPLETE;
          break;
        case "NEW":
        case "SENT":
        case "CREATED":
        case "WAITING":
        case "PENDING":
          status = FundingRecord.Status.PROCESSING;
          break;
        default:
          status = FundingRecord.Status.FAILED;
      }

      String transactionId = Long.toString(entry.getTransactionId());

      String description = entry.getDescription();

      String feeCurrency = entry.getFeeCurrency();
      String externalId = null;
      if (entry.getTransactionType().equals("DEPOSIT")
          && description.startsWith(feeCurrency + ": ")) {
        externalId =
            description.replace(
                feeCurrency + ": ", ""); // the transaction hash is in the description
      }
      String address = null;
      String addressTag = null;
      if (entry.getTransactionType().equals("WITHDRAWAL")
          || entry.getTransactionType().equals("DEPOSIT")) {
        for (CoinmateTransferHistoryEntry transfer : additionalTransferData.getData()) {
          if (transfer != null && transfer.getId() == entry.getTransactionId()) {
            address = transfer.getDestination();
            addressTag = transfer.getDestinationTag();
          }
        }
      }

      FundingRecord funding =
          FundingRecord.builder()
              .address(address)
              .addressTag(addressTag)
              .date(new Date(entry.getTimestamp()))
              .currency(Currency.getInstance(entry.getAmountCurrency()))
              .amount(entry.getAmount())
              .internalId(transactionId)
              .blockchainTransactionHash(externalId)
              .type(type)
              .status(status)
              .fee(entry.getFee())
              .description(description)
              .build();

      fundings.add(funding);
    }

    return fundings;
  }

  public static List<FundingRecord> adaptFundingDetail(
      CoinmateTransferDetail coinmateTransferDetail) {
    CoinmateTransferHistoryEntry entry = coinmateTransferDetail.getData();

    FundingRecord.Type type;
    switch (entry.getTransferType()) {
      case "WITHDRAWAL":
      case "CREATE_VOUCHER":
        type = FundingRecord.Type.WITHDRAWAL;
        break;
      case "DEPOSIT":
      case "USED_VOUCHER":
      case "NEW_USER_REWARD":
      case "REFERRAL":
        type = FundingRecord.Type.DEPOSIT;
        break;
      default:
        // here we ignore the other types which are trading
        return Collections.emptyList();
    }

    FundingRecord.Status status;
    switch (entry.getTransferStatus().toUpperCase()) {
      case "OK":
      case "COMPLETED":
        status = FundingRecord.Status.COMPLETE;
        break;
      case "NEW":
      case "SENT":
      case "CREATED":
      case "WAITING":
      case "PENDING":
        status = FundingRecord.Status.PROCESSING;
        break;
      default:
        status = FundingRecord.Status.FAILED;
    }

    FundingRecord funding =
        FundingRecord.builder()
            .address(entry.getDestination())
            .addressTag(entry.getDestinationTag())
            .date(new Date(entry.getTimestamp()))
            .currency(Currency.getInstance(entry.getAmountCurrency()))
            .amount(entry.getAmount())
            .internalId(Long.toString(entry.getId()))
            .type(type)
            .status(status)
            .fee(entry.getFee())
            .build();

    return Collections.singletonList(funding);
  }

  public static List<LimitOrder> adaptOpenOrders(CoinmateOpenOrders coinmateOpenOrders)
      throws CoinmateException {

    List<LimitOrder> ordersList = new ArrayList<>(coinmateOpenOrders.getData().size());

    for (CoinmateOpenOrdersEntry entry : coinmateOpenOrders.getData()) {

      Order.OrderType orderType;
      // TODO
      if ("BUY".equals(entry.getType())) {
        orderType = Order.OrderType.BID;
      } else if ("SELL".equals(entry.getType())) {
        orderType = Order.OrderType.ASK;
      } else {
        throw new CoinmateException("Unknown order type");
      }

      LimitOrder limitOrder =
          new LimitOrder(
              orderType,
              entry.getAmount(),
              CoinmateUtils.getPair(entry.getCurrencyPair()),
              Long.toString(entry.getId()),
              new Date(entry.getTimestamp()),
              entry.getPrice());

      ordersList.add(limitOrder);
    }

    return ordersList;
  }

  public static List<Order> adaptStopOrders(CoinmateOpenOrders coinmateOpenOrders)
      throws CoinmateException {

    return coinmateOpenOrders.getData().stream()
        .filter(entry -> "LIMIT_STOP".equals(entry.getOrderTradeType()))
        .map(
            entry ->
                new StopOrder(
                    "SELL".equals(entry.getType()) ? Order.OrderType.ASK : Order.OrderType.BID,
                    entry.getAmount(),
                    CoinmateUtils.getPair(entry.getCurrencyPair()),
                    Long.toString(entry.getId()),
                    new Date(entry.getTimestamp()),
                    entry.getStopPrice(),
                    entry.getPrice(),
                    null,
                    null,
                    null))
        .collect(Collectors.toList());
  }

  public static String adaptSortOrder(TradeHistoryParamsSorted.Order order) {
    switch (order) {
      case asc:
        return "ASC";
      case desc:
        return "DESC";
      default:
        throw new IllegalArgumentException();
    }
  }

  private static BigDecimal getCumulativeAmount(
      BigDecimal originalAmount, BigDecimal remainingAmount) {
    return (originalAmount != null && remainingAmount != null)
        ? originalAmount.subtract(remainingAmount)
        : null;
  }

  /**
   * Adapt a single order.
   *
   * @param coinmateOrder The raw order
   * @param orderByIdFetcher function to fetch order by id - needed to fetch market orders generated
   *     by stop-loss orders.
   * @return
   */
  public static Order adaptOrder(
      CoinmateOrderHistoryEntry coinmateOrder,
      Function<String, CoinmateOrderHistoryEntry> orderByIdFetcher)
      throws CoinmateException {

    Order.OrderType orderType;

    if ("BUY".equals(coinmateOrder.getType())) {
      orderType = Order.OrderType.BID;
    } else if ("SELL".equals(coinmateOrder.getType())) {
      orderType = Order.OrderType.ASK;
    } else {
      throw new CoinmateException("Unknown order type");
    }
    Order.OrderStatus orderStatus;
    if ("CANCELLED".equals(coinmateOrder.getStatus())) {
      if (coinmateOrder.getStopLossOrderId() != null) {
        // this is a stopped order
        orderStatus = Order.OrderStatus.STOPPED;
      } else {
        orderStatus = Order.OrderStatus.CANCELED;
      }
    } else if ("FILLED".equals(coinmateOrder.getStatus())) {
      orderStatus = Order.OrderStatus.FILLED;
    } else if ("PARTIALLY_FILLED".equals(coinmateOrder.getStatus())) {
      orderStatus = Order.OrderStatus.PARTIALLY_FILLED;
    } else if ("OPEN".equals(coinmateOrder.getStatus())) {
      orderStatus = Order.OrderStatus.NEW;
    } else {
      orderStatus = Order.OrderStatus.UNKNOWN;
    }

    BigDecimal originalAmount;
    BigDecimal remainingAmount;
    BigDecimal averagePrice;
    BigDecimal cumulativeAmount;

    if (orderStatus == Order.OrderStatus.STOPPED) {
      // fetch generated market order and get its average price and amount
      CoinmateOrderHistoryEntry marketOrderRaw =
          orderByIdFetcher.apply(coinmateOrder.getStopLossOrderId());
      if (marketOrderRaw == null) {
        throw new CoinmateException("Failed to fetch market order generated by stoploss order.");
      }
      averagePrice = marketOrderRaw.getAvgPrice();
      originalAmount = marketOrderRaw.getOriginalAmount();
      remainingAmount = marketOrderRaw.getRemainingAmount();
      cumulativeAmount = marketOrderRaw.getCumulativeAmount();
    } else {
      averagePrice = coinmateOrder.getAvgPrice();
      originalAmount = coinmateOrder.getOriginalAmount();
      remainingAmount = coinmateOrder.getRemainingAmount();
      cumulativeAmount = coinmateOrder.getCumulativeAmount();
    }

    // TODO: we can probably use `orderTradeType` to distinguish between Market and Limit order
    return new MarketOrder(
        orderType,
        originalAmount,
        null,
        Long.toString(coinmateOrder.getId()),
        new Date(coinmateOrder.getTimestamp()),
        averagePrice,
        cumulativeAmount == null
            ? getCumulativeAmount(originalAmount, remainingAmount)
            : cumulativeAmount,
        null,
        orderStatus,
        null);
  }

  public static Ticker adaptTradeStatistics(
      CoinmateTradeStatistics tradeStatistics, CurrencyPair currencyPair) {
    return new Ticker.Builder()
        .currencyPair(currencyPair)
        .last(tradeStatistics.getLastRealizedTrade())
        .high(tradeStatistics.getHigh24hours())
        .low(tradeStatistics.getLow24hours())
        .volume(tradeStatistics.getVolume24Hours())
        .open(tradeStatistics.getTodaysOpen())
        .percentageChange(tradeStatistics.getDailyChange())
        .build();
  }

  /**
   * Adapts CoinmateCurrencies to XChange CurrencyMetaData map.
   *
   * @param coinmateCurrencies The Coinmate currency information
   * @return Map of Currency to CurrencyMetaData
   */
  public static Map<Currency, CurrencyMetaData> adaptCurrencies(
      CoinmateCurrencies coinmateCurrencies) {
    if (coinmateCurrencies == null || coinmateCurrencies.getData() == null) {
      return Collections.emptyMap();
    }

    Map<Currency, CurrencyMetaData> currencies = new java.util.HashMap<>();

    for (CoinmateCurrencyInfo currencyInfo : coinmateCurrencies.getData()) {
      Currency currency = Currency.getInstance(currencyInfo.getCurrency());

      // Get withdrawal fee from first network if available
      BigDecimal withdrawalFee = null;
      BigDecimal minWithdrawalAmount = null;

      if (currencyInfo.getNetworks() != null && !currencyInfo.getNetworks().isEmpty()) {
        // Use first network's withdrawal info
        var network = currencyInfo.getNetworks().get(0);
        if (network.getWithdraw() != null && network.getWithdraw().isEnabled()) {
          minWithdrawalAmount = network.getWithdraw().getMinAmount();

          // Get first fee variant
          if (network.getWithdraw().getFee() != null && !network.getWithdraw().getFee().isEmpty()) {
            CoinmateWithdrawFee feeInfo = network.getWithdraw().getFee().get(0);
            withdrawalFee = feeInfo.getFixFee();
          }
        }
      }

      CurrencyMetaData metadata =
          getCurrencyMetaData(currencyInfo, withdrawalFee, minWithdrawalAmount);

      currencies.put(currency, metadata);
    }

    return currencies;
  }

  /**
   * Adapts CoinmateTradingPairs to XChange InstrumentMetaData map.
   *
   * @param coinmateTradingPairs The Coinmate trading pairs information
   * @return Map of Instrument to InstrumentMetaData
   */
  public static Map<Instrument, InstrumentMetaData> adaptTradingPairs(
      CoinmateTradingPairs coinmateTradingPairs) {
    if (coinmateTradingPairs == null || coinmateTradingPairs.getData() == null) {
      return Collections.emptyMap();
    }

    Map<Instrument, InstrumentMetaData> pairs = new java.util.HashMap<>();

    for (CoinmateTradingPairsEntry pairInfo : coinmateTradingPairs.getData()) {
      CurrencyPair currencyPair = CoinmateUtils.getPair(pairInfo.getName());

      InstrumentMetaData metadata =
          new InstrumentMetaData(
              null, // tradingFee
              null, // feeTiers
              BigDecimal.valueOf(pairInfo.getMinAmount()), // minimumAmount
              null, // maximumAmount
              null, // counterMinimumAmount
              null, // counterMaximumAmount
              pairInfo.getPriceScale(), // priceScale
              pairInfo.getVolumeScale(), // volumeScale
              null, // amountStepSize
              null, // priceStepSize
              null, // tradingFeeCurrency
              true, // marketOrderEnabled - Coinmate supports market orders
              null // contractValue
              );

      pairs.put(currencyPair, metadata);
    }

    return pairs;
  }

  /**
   * Adapts Coinmate metadata to XChange ExchangeMetaData. Similar to Kraken's
   * adaptToExchangeMetaData method.
   *
   * @param originalMetaData Original metadata (for rate limits)
   * @param coinmateCurrencies Coinmate currency information
   * @param coinmateTradingPairs Coinmate trading pairs information
   * @return ExchangeMetaData with adapted information
   */
  public static ExchangeMetaData adaptToExchangeMetaData(
      ExchangeMetaData originalMetaData,
      CoinmateCurrencies coinmateCurrencies,
      CoinmateTradingPairs coinmateTradingPairs) {

    Map<Currency, CurrencyMetaData> currencies = adaptCurrencies(coinmateCurrencies);
    Map<Instrument, InstrumentMetaData> pairs = adaptTradingPairs(coinmateTradingPairs);

    return new ExchangeMetaData(
        pairs,
        currencies,
        originalMetaData == null ? null : originalMetaData.getPublicRateLimits(),
        originalMetaData == null ? null : originalMetaData.getPrivateRateLimits(),
        originalMetaData == null ? null : originalMetaData.isShareRateLimits());
  }

  private static CurrencyMetaData getCurrencyMetaData(
      CoinmateCurrencyInfo currencyInfo, BigDecimal withdrawalFee, BigDecimal minWithdrawalAmount) {
    boolean depositsEnabled = currencyInfo.isDepositEnabled();
    boolean withdrawalsEnabled = currencyInfo.isWithdrawEnabled();
    WalletHealth walletHealth;
    if (depositsEnabled && withdrawalsEnabled) {
      walletHealth = WalletHealth.ONLINE;
    } else if (!depositsEnabled && !withdrawalsEnabled) {
      walletHealth = WalletHealth.OFFLINE;
    } else if (depositsEnabled) {
      walletHealth = WalletHealth.WITHDRAWALS_DISABLED;
    } else {
      walletHealth = WalletHealth.DEPOSITS_DISABLED;
    }

    return new CurrencyMetaData(
        currencyInfo.getPrecision(), withdrawalFee, minWithdrawalAmount, walletHealth);
  }
}
